package wchat

import (
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"gamesdk/common/log"
	"io/ioutil"
	"net/http"
	"regexp"
	"strings"
)

//UserInfo 用户信息
type UserInfo struct {
	OpenID    string `json:"openId"`
	NickName  string `json:"nickName"`
	Gender    int    `json:"gender"`
	Language  string `json:"language"`
	City      string `json:"city"`
	Province  string `json:"province"`
	Country   string `json:"country"`
	AvatarURL string `json:"avatarUrl"`
	UnionID   string `json:"unionId"`
	Watermark struct {
		Timestamp int    `json:"timestamp"`
		Appid     string `json:"appid"`
	} `json:"watermark"`
}

//GetEngine 获取微信对象
func GetEngine() Inter {
	return _wchat
}

//InitWchat 初始化
func InitWchat(wxAppID, wxAppSecret string) {
	if _wchat == nil {
		_wchat = &_Wchat{
			WxAppID:     wxAppID,
			WxAppSecret: wxAppSecret,
		}
	}
}

//Inter 接口
type Inter interface {
	//WxLogin 微信登录
	//uid:"用户id",
	//code:"登陆凭证",
	//encryptedData:"加密字符串",
	//iv:"初始向量"
	WxLogin(uid, code, encryptedData, iv string) (*UserInfo, error)
}

type _Wchat struct {
	WxAppID     string
	WxAppSecret string
}

var _wchat *_Wchat

type _WChatJScode2Session struct {
	//用户唯一标识
	Openid string `json:"openid"`
	//会话密钥
	SessionKey string `json:"session_key"`
	//用户在开放平台的唯一标识符，在满足 UnionID 下发条件的情况下会返回，详见 UnionID 机制说明。
	Unionid string `json:"unionid"`
	//错误码
	Errcode int `json:"errcode"`
	//错误信息
	Errmsg string `json:"Errcode"`
}

//WxLogin 微信登录
//uid:"用户id",
//code:"登陆凭证",
//encryptedData:"加密字符串",
//iv:"初始向量"
func (pointer *_Wchat) WxLogin(uid, code, encryptedData, iv string) (*UserInfo, error) {
	if len(code) <= 0 {
		return nil, errors.New("code <= 0")
	}
	if len(encryptedData) <= 0 {
		return nil, errors.New("encryptedData <= 0")
	}
	if len(iv) <= 0 {
		return nil, errors.New("iv <= 0")
	}

	resp, err := http.Get("https://api.weixin.qq.com/sns/jscode2session?appid=" + pointer.WxAppID + "&secret=" + pointer.WxAppSecret + "&js_code=" + code + "&grant_type=authorization_code")
	if err != nil {
		log.Error(err.Error())
		return nil, err
	}
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Error(err.Error())
		return nil, err
	}
	respJSON := &_WChatJScode2Session{}
	err = json.Unmarshal(respBody, respJSON)
	if err != nil {
		log.Error(err.Error())
		return nil, err
	}
	// log.Debug("%v", respJSON)
	if respJSON.Errcode != 0 {
		return nil, fmt.Errorf("code:%d,msg:%s", respJSON.Errcode, respJSON.Errmsg)
	}
	return _Decrypt(pointer.WxAppID, respJSON.SessionKey, encryptedData, iv)
}

// Decrypt wchat 解密用户加密数据
func _Decrypt(appid, sessionkey, encryptedData string, iv string) (*UserInfo, error) {
	sessionKey := strings.Replace(strings.TrimSpace(sessionkey), " ", "+", -1)
	if len(sessionKey) != 24 {
		return nil, errors.New("sessionKey length is error")
	}
	aesKey, err := base64.StdEncoding.DecodeString(sessionKey)
	if err != nil {
		return nil, err
	}
	iv = strings.Replace(strings.TrimSpace(iv), " ", "+", -1)
	if len(iv) != 24 {
		return nil, errors.New("iv length is error")
	}
	aesIv, err := base64.StdEncoding.DecodeString(iv)
	if err != nil {
		return nil, err
	}
	encryptedData = strings.Replace(strings.TrimSpace(encryptedData), " ", "+", -1)
	aesCipherText, err := base64.StdEncoding.DecodeString(encryptedData)
	if err != nil {
		return nil, err
	}
	aesPlantText := make([]byte, len(aesCipherText))

	aesBlock, err := aes.NewCipher(aesKey)
	if err != nil {
		return nil, err
	}

	mode := cipher.NewCBCDecrypter(aesBlock, aesIv)
	mode.CryptBlocks(aesPlantText, aesCipherText)
	aesPlantText = _PKCS7UnPadding(aesPlantText)

	var decrypted map[string]interface{}

	re := regexp.MustCompile(`[^\{]*(\{.*\})[^\}]*`)
	aesPlantText = []byte(re.ReplaceAllString(string(aesPlantText), "$1"))
	err = json.Unmarshal(aesPlantText, &decrypted)
	if err != nil {
		return nil, err
	}

	if decrypted["watermark"].(map[string]interface{})["appid"] != appid {
		return nil, errors.New("appId is not match")
	}
	info := &UserInfo{}
	err = json.Unmarshal(aesPlantText, info)
	return info, err
}

// PKCS7UnPadding return unpadding []Byte plantText
func _PKCS7UnPadding(plantText []byte) []byte {
	length := len(plantText)
	if length > 0 {
		unPadding := int(plantText[length-1])
		return plantText[:(length - unPadding)]
	}
	return plantText
}
